package gui.control;

import java.util.List;

import decoder.Decoder;
import extern.COMPort;
import gui.PICView;
import gui.SourceCodeTableModel;
import java.io.File;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;
import structure.Byte;
import structure.Importer;
import structure.Interrupt;
import structure.Line;
import structure.PICStack;
import structure.Parser;
import structure.Register;
import structure.WRegister;

/**
 * Enthält den Timer, hält die Daten für das Verzeichnis der Quellcodedatei, sowie
 * Listen zur Quellcodedarstellung in der GUI.
 * Instanziert die GUI und kommuniziert mit dieser Instanz. Aktualisiert dessen Werte
 * für Register, Port und Stack.
 * Zudem werden in dieser Klasse die Programmschritte ausgeführt. Ausführung erfolgt
 * entweder schrittweise oder automatisch bis zu einem Breakpoint oder bis auf der GUI-Stop geklickt wurde.
 * @author Patrick Lehmann und Dorothee Kern.
 */
public class Pic {

    /**
     * Speichert verstrichene Zeit in Maschinenzyklen
     */
    public static int time = 0;
    /**
     * Verzeichnispfad zum LST-File, wird von GUI geschrieben.
     */
    private File dir = null;
    /**
     * Instanz der GUI
     */
    private PICView picView = new PICView();
    /**
     * Singleton Instanz
     */
    private static Pic instance = null;
    /**
     * Enthält den impotierten Sourcecode. Wird für den Parser benötigt.
     */
    private List<String> importedSourceCode = null;
    /**
     * Ergebnis von {@link Parser}. Liste zur Darstellung in der GUI, die alle
     * eingelesenen Programmzeilen enthält.
     */
    private List<Line> listForGui = null;
    /**
     * Ergebnis von {@link Parser}. Liste für interne Darstellung. Enthält nur die
     * die Quellcodezeilen, die auch einen OpCode enthalten.
     */
    private List<Line> listForDecoder = null;
    /**
     * Array der Breakpoints aus der GUI.
     */
    private boolean[] breakpoints = null;
    /**
     * Stop-Flag, das die Programmausführung anhält oder starten lässt.
     */
    private boolean stop = false;
    /**
     * Decoder-Instanz.
     */
    private Decoder decoder = new Decoder();
    /**
     * Parser-Instanz.
     */
    private Parser parser = new Parser();
    /**
     * Importer-Instanz.
     */
    private Importer importer = new Importer();
    /**
     * Interrupt-Instanz.
     */
    private Interrupt interrupt = new Interrupt();
    /**
     * COMPort-Instanz.
     */
    private COMPort com = null;

    ;

    /**
     * Main-Methode. Erstellt eine Pic-Instanz.
     * @param args
     */
    public static void main(String[] args) {
        getInstance();
    }

    /**
     * Der Konstruktor ist private für Singleton-Pattern.
     * Startet GUI und erneuert die Ausgaben auf der GUI für
     * Registertabellen, Latch, Prescaler, und Ports.
     */
    private Pic() {
        java.awt.EventQueue.invokeLater(new Runnable() {

            public void run() {
                picView.setVisible(true);
            }
        });

        refreshRegisters();
        refreshLatch();
        refreshPrescaler();
        refreshPorts();
    }

    /**
     * Die Methode stellt sicher, dass nur eine Instanz erzeugt werden kann.
     * @return Globale Pic-Instanz.
     */
    public static Pic getInstance() {
        if (instance == null) {
            instance = new Pic();
        }
        return instance;
    }

    /**
     * Start Methode, führt Programmbefehle aus, bis ein Breakpoint erreicht wurde oder
     * Pause auf der GUI geklickt wurde. Es werden nur Breakpoints berücksichtigt,
     * die vor ausführen der Methode gesetzt wurden.
     * @throws InterruptedException
     */
    public void start() throws InterruptedException {
        //aktuelle Breakpoints einlesen.
        breakpoints = picView.getBreakpoints();
        Line line = null;

        do {
            Thread.sleep(100);
            //Nächsten Befehl laden
            line = listForDecoder.get(Register.getInstance().getProgramCounter());
            step();
        } //Solange kein Breakpoint oder Pause gedrückt  wurde:
        //step() ausführen.
        while (!isBreakpoint(line) && !stop);
        //Flag zurücksetzen.
        stop = false;
    }

    /**
     * Führt einen Programmschritt aus.
     */
    public void step() {
        //Befehl an Hand des PC laden
        Line element = listForDecoder.get(Register.getInstance().getProgramCounter());
        //Zeile markieren
        //TODO evtl durch TableCellRenderer ersetzen
        picView.getSourceCodeTable().changeSelection(element.getLineNumber() - 1, 0, false, false);
        //Register auf GUI aktualisieren
        refreshRegisters();
        //Ports aktualisieren
        refreshLatch();
        refreshPrescaler();
        refreshPorts();
        //Kommunikation mit COM-Port, wenn das dazugehörige Objekt instanziiert wurde.
        if (com != null) {
            refreshComPort();
        }
        //Abschließend Befehl decodieren
        decoder.decodeOpCode(element);
    }

    /**
     * Setzt das Register, W-Register, Stack, PC und Time zurück und leert den Stack.
     * Setzt den COM-Port zurück.
     * Füllt auf der GUI die SourceCodeTable und die Register neu.
     */
    public void reset() {
        Register.getInstance().reset();
        WRegister.getInstance().setWReg(0);
        PICStack.getInstance().clear();
        time = 0;
        fillTable();

        refreshRegisters();
        refreshLatch();
        refreshPrescaler();
        refreshPorts();

        com = null;
    }

    /**
     *
     * @return  Verzeichnispfad zur LST-Datei.
     */
    public File getDir() {
        return dir;
    }

    /**
     * Setzt das dir-Attribut, sowie die Attribute listForDecoder und listForGui
     * @param dir Verzeichnispfad zur LST-Datei.
     */
    public void setDir(File dir) {
        this.dir = dir;

        setImportedSourceCode();
        setListForDecoder();
        setListForGui();
    }

    /**
     * Gibt eine Liste für die Darstellung in der GUI zurück.
     * @return Liste, die die Attribute Zeilennummer und Programmcode enthält
     */
    public List<Line> getListForGui() {

        return listForGui;
    }

    /**
     * Gibt eine Liste für die interne Darstellung zurück.
     * @return Liste, die die Attribute ProgramCounter, OpCode, Zeilennummer und Programmcode enthält
     */
    public List<Line> getListForDecoder() {

        return listForDecoder;
    }

    /**
     * Gibt die Registerwerte, den Stack und die Ports in den Tabellen und Textfeldern auf der GUI aus.
     */
    public void refreshRegisters() {
        int row, column;
        int i = 0;
        //Bank 0 ausgeben
        for (row = 0; row < 4; row++) {
            for (column = 1; column <= 16; column++) {
                String value = Integer.toHexString(Register.getInstance().getRegisterByteForGUI(i));
                picView.getRegTableBank0().setValueAt(value, row, column);
                i++;
            }
        }
        //Bank 1 ausgeben
        i = 0x80;
        for (row = 0; row < 4; row++) {
            for (column = 1; column <= 16; column++) {
                String value = Integer.toHexString(Register.getInstance().getRegisterByteForGUI(i));
                picView.getRegTableBank1().setValueAt(value, row, column);
                i++;
            }
        }

        //Textfelder füllen
        //PC
        String value = Integer.toString(Register.getInstance().getProgramCounter());
        picView.getTxtPC().setText(value);
        //Status
        value = Integer.toHexString(Register.getInstance().getRegisterByteForGUI(0x3));
        picView.getTxtStatus().setText(value);
        //FSR
        value = Integer.toHexString(Register.getInstance().getRegisterByteForGUI(0x4));
        picView.getTxtFSR().setText(value);
        //Option
        value = Integer.toHexString(Register.getInstance().getRegisterByteForGUI(0x81));
        picView.getTxtOpt().setText(value);
        //W-Reg
        value = Integer.toHexString(WRegister.getInstance().getWRegAsInt());
        picView.getTxtW().setText(value);
        //Timer
        picView.getTxtTime().setText(Integer.toString(time));
        //WDT
        //picView.getTxtWdt().setText(Register.getInstance().

        //Stacktabelle leeren
        for (int j = 0; j < picView.getStackTable().getRowCount(); j++) {
            picView.getStackTable().setValueAt(" ", j, 0);
        }

        //Stack ausgeben
        Stack<Integer> stack = PICStack.getInstance().clone();
        i = 0;
        while (!stack.isEmpty()) {
            value = Integer.toHexString(stack.pop());
            picView.getStackTable().setValueAt(value, i, 0);
            i++;
        }
    }

    /**
     * Setzt die Latch-Register auf der GUI.
     */
    public void refreshLatch() {
        Byte latchA = Register.getInstance().getLatchA();
        Byte latchB = Register.getInstance().getLatchB();

        picView.getBoxLatchA0().setSelected(latchA.getBit(0));
        picView.getBoxLatchA1().setSelected(latchA.getBit(1));
        picView.getBoxLatchA2().setSelected(latchA.getBit(2));
        picView.getBoxLatchA3().setSelected(latchA.getBit(3));
        picView.getBoxLatchA4().setSelected(latchA.getBit(4));

        picView.getBoxLatchB0().setSelected(latchB.getBit(0));
        picView.getBoxLatchB1().setSelected(latchB.getBit(1));
        picView.getBoxLatchB2().setSelected(latchB.getBit(2));
        picView.getBoxLatchB3().setSelected(latchB.getBit(3));
        picView.getBoxLatchB4().setSelected(latchB.getBit(4));
        picView.getBoxLatchB5().setSelected(latchB.getBit(5));
        picView.getBoxLatchB6().setSelected(latchB.getBit(6));
        picView.getBoxLatchB7().setSelected(latchB.getBit(7));
    }

    /**
     * Setzt die Ports auf die aktuellen Werte.
     * Wenn ein Portpin Ausgang ist, so ist die zugehörige Checkbox nicht aktivierbar.
     */
    public void refreshPorts() {
        boolean value, tris;
        //*************************************************************
        //RA
        //*************************************************************
        //Port A,0 auslesen
        value = Register.getInstance().getRegisterBitForGUI(0x5, 0);
        //Tris A,0 auslesen
        tris = Register.getInstance().getRegisterBitForGUI(0x85, 0);
        //Wenn tris = true ist, dann ist Pin  ein Eingang, somit
        //soll die Checkbox anwählbar sein
        //und den Wert aus dem Register enthalten.

        //Fall Ausgang
        if (!tris) {
            picView.getBoxA0().setEnabled(false);
            picView.getBoxA0().setSelected(value);
        } //Fall Eingang
        else {
            picView.getBoxA0().setEnabled(true);
            Register.getInstance().setRegisterBit(0x5, 0, picView.getBoxA0().isSelected());

        }

        //Port A,1 auslesen
        value = Register.getInstance().getRegisterBitForGUI(0x5, 1);
        //Tris A,1 auslesen
        tris = Register.getInstance().getRegisterBitForGUI(0x85, 1);
        if (!tris) {
            picView.getBoxA1().setEnabled(false);
            picView.getBoxA1().setSelected(value);
        } else {
            picView.getBoxA1().setEnabled(true);
            Register.getInstance().setRegisterBitForGUI(0x5, 1, picView.getBoxA1().isSelected());

        }

        //Port A,2 auslesen
        value = Register.getInstance().getRegisterBitForGUI(0x5, 2);
        //Tris A,2 auslesen
        tris = Register.getInstance().getRegisterBitForGUI(0x85, 2);
        if (!tris) {
            picView.getBoxA2().setEnabled(false);
            picView.getBoxA2().setSelected(value);
        } else {
            picView.getBoxA2().setEnabled(true);
            Register.getInstance().setRegisterBitForGUI(0x5, 2, picView.getBoxA2().isSelected());

        }

        //Port A,3 auslesen
        value = Register.getInstance().getRegisterBitForGUI(0x5, 3);
        //Tris A,3 auslesen
        tris = Register.getInstance().getRegisterBitForGUI(0x85, 3);
        if (!tris) {
            picView.getBoxA3().setEnabled(false);
            picView.getBoxA3().setSelected(value);
        } else {
            picView.getBoxA3().setEnabled(true);
            Register.getInstance().setRegisterBitForGUI(0x5, 3, picView.getBoxA3().isSelected());

        }

        //Port A,4 auslesen
        value = Register.getInstance().getRegisterBitForGUI(0x5, 4);
        //Tris A,4 auslesen
        tris = Register.getInstance().getRegisterBitForGUI(0x85, 4);
        if (!tris) {
            picView.getBoxA4().setEnabled(false);
            picView.getBoxA4().setSelected(value);
        } else {
            picView.getBoxA4().setEnabled(true);
            Register.getInstance().setRegisterBitForGUI(0x5, 4, picView.getBoxA4().isSelected());

        }

        //*************************************************************
        //RB
        //*************************************************************
        //Port B,0 auslesen
        value = Register.getInstance().getRegisterBitForGUI(0x6, 0);
        //Tris B,0 auslesen
        tris = Register.getInstance().getRegisterBitForGUI(0x86, 0);
        //Wenn tris = true ist, dann ist Pin  ein Eingang, somit
        //soll die Checkbox anwählbar sein
        //und den Wert aus dem Register enthalten.
        if (!tris) {
            picView.getBoxB0().setEnabled(false);
            picView.getBoxB0().setSelected(value);
        } else {
            picView.getBoxB0().setEnabled(true);
            Register.getInstance().setRegisterBitForGUI(0x6, 0, picView.getBoxB0().isSelected());

        }

        //Port B,1 auslesen
        value = Register.getInstance().getRegisterBitForGUI(0x6, 1);
        //Tris B,1 auslesen
        tris = Register.getInstance().getRegisterBitForGUI(0x86, 1);
        if (!tris) {
            picView.getBoxB1().setEnabled(false);
            picView.getBoxB1().setSelected(value);
        } else {
            picView.getBoxB1().setEnabled(true);
            Register.getInstance().setRegisterBitForGUI(0x6, 1, picView.getBoxB1().isSelected());

        }

        //Port B,2 auslesen
        value = Register.getInstance().getRegisterBitForGUI(0x6, 2);
        //Tris B,2 auslesen
        tris = Register.getInstance().getRegisterBitForGUI(0x86, 2);
        if (!tris) {
            picView.getBoxB2().setEnabled(false);
            picView.getBoxB2().setSelected(value);
        } else {
            picView.getBoxB2().setEnabled(true);
            Register.getInstance().setRegisterBitForGUI(0x6, 2, picView.getBoxB2().isSelected());

        }

        //Port B,3 auslesen
        value = Register.getInstance().getRegisterBitForGUI(0x6, 3);
        //Tris B,3 auslesen
        tris = Register.getInstance().getRegisterBitForGUI(0x86, 3);
        if (!tris) {
            picView.getBoxB3().setEnabled(false);
            picView.getBoxB3().setSelected(value);
        } else {
            picView.getBoxB3().setEnabled(true);
            Register.getInstance().setRegisterBitForGUI(0x6, 3, picView.getBoxB3().isSelected());

        }

        //Port B,4 auslesen
        value = Register.getInstance().getRegisterBitForGUI(0x6, 4);
        //Tris B, auslesen
        tris = Register.getInstance().getRegisterBitForGUI(0x86, 4);
        if (!tris) {
            picView.getBoxB4().setEnabled(false);
            picView.getBoxB4().setSelected(value);
        } else {
            picView.getBoxB4().setEnabled(true);
            Register.getInstance().setRegisterBitForGUI(0x6, 4, picView.getBoxB4().isSelected());

        }

        //Port B,5 auslesen
        value = Register.getInstance().getRegisterBitForGUI(0x6, 5);
        //Tris B,5 auslesen
        tris = Register.getInstance().getRegisterBitForGUI(0x86, 5);
        if (!tris) {
            picView.getBoxB5().setEnabled(false);
            picView.getBoxB5().setSelected(value);
        } else {
            picView.getBoxB5().setEnabled(true);
            Register.getInstance().setRegisterBitForGUI(0x6, 5, picView.getBoxB5().isSelected());

        }

        //Port B,6 auslesen
        value = Register.getInstance().getRegisterBitForGUI(0x6, 6);
        //Tris B,6 auslesen
        tris = Register.getInstance().getRegisterBitForGUI(0x86, 6);
        if (!tris) {
            picView.getBoxB6().setEnabled(false);
            picView.getBoxB6().setSelected(value);
        } else {
            picView.getBoxB6().setEnabled(true);
            Register.getInstance().setRegisterBitForGUI(0x6, 6, picView.getBoxB6().isSelected());

        }

        //Port B,7 auslesen
        value = Register.getInstance().getRegisterBitForGUI(0x6, 7);
        //Tris B,7 auslesen
        tris = Register.getInstance().getRegisterBitForGUI(0x86, 7);
        if (!tris) {
            picView.getBoxB7().setEnabled(false);
            picView.getBoxB7().setSelected(value);
        } else {
            picView.getBoxB7().setEnabled(true);
            Register.getInstance().setRegisterBitForGUI(0x6, 7, picView.getBoxB7().isSelected());

        }



    }

    /**
     * Aktualisiert die Prescaler Textfelder auf der GUI.
     */
    public void refreshPrescaler() {
        //den Wert des Prescalers (Bit0-2) in die lokale Variable schreiben
        int prescaler = Register.getInstance().getRegisterByteAsInt(0x81) & 0x7;
        String txt = "";

        //PSA = true, dann hat WDT den Prescaler
        if (Register.getInstance().getRegisterBit(0x81, 3) == true) {
            picView.getTxtPrescaler().setText("Watchdog");
            switch (prescaler) {
                case 0:
                    txt = "1 : 1";
                    break;
                case 1:
                    txt = "1 : 2";
                    break;
                case 2:
                    txt = "1 : 4";
                    break;
                case 3:
                    txt = "1 : 8";
                    break;
                case 4:
                    txt = "1 : 16";
                    break;
                case 5:
                    txt = "1 : 32";
                    break;
                case 6:
                    txt = "1 : 64";
                    break;
                case 7:
                    txt = "1 : 128";
                    break;
            }
        } //sonst hat TMR0 den Prescaler
        else {
            picView.getTxtPrescaler().setText("TMR0");
            switch (prescaler) {
                case 0:
                    txt = "1 : 2";
                    break;
                case 1:
                    txt = "1 : 4";
                    break;
                case 2:
                    txt = "1 : 8";
                    break;
                case 3:
                    txt = "1 : 16";
                    break;
                case 4:
                    txt = "1 : 32";
                    break;
                case 5:
                    txt = "1 : 64";
                    break;
                case 6:
                    txt = "1 : 128";
                    break;
                case 7:
                    txt = "1 : 256";
                    break;
            }
        }
        picView.getTxtRatio().setText(txt);
    }

    private void setListForDecoder() {
        listForDecoder = parser.parseLST(importedSourceCode);
    }

    private void setListForGui() {
        listForGui = parser.parseLSTforGUI(importedSourceCode);
    }

    private void setImportedSourceCode() {
        importedSourceCode = importer.importLST(dir.getPath());
    }

    /**
     * Setzt neues TableModel, füllt die JTable mit Werten aus eingelesem File und
     * setzt weitere Werte.
     */
    public void fillTable() {

        SourceCodeTableModel tableModel = new SourceCodeTableModel();
        //RegisterTableModel regTableModel = new RegisterTableModel();
        tableModel.setDataVector(Pic.getInstance().getListForGui());
        picView.getSourceCodeTable().setModel(tableModel);

        picView.getSourceCodeTable().getColumnModel().getColumn(0).setPreferredWidth(20);
        picView.getSourceCodeTable().getColumnModel().getColumn(1).setPreferredWidth(20);
        picView.getSourceCodeTable().getColumnModel().getColumn(2).setPreferredWidth(250);

        Line element = listForDecoder.get(Register.getInstance().getProgramCounter());
        picView.getSourceCodeTable().changeSelection(element.getLineNumber() - 1, 0, false, false);

    }

    /**
     * Prüft, ob bei der aktuellen Programmzeile ein Breakpoint gesetzt ist.
     * @param line
     * @return
     */
    private boolean isBreakpoint(Line line) {
        if (breakpoints[line.getLineNumber() - 1] == true) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Erhöht den Programmlaufzeitzähler und überprüft, ob
     * ein Interrupt aufgetreten ist.
     */
    public void incTime() {
        time++;
        interrupt.checkInterrupt();

    }

    /**
     * Setzt das Stop-Flag, um die Start-Methode anzuhalten.
     * @param stop the stop to set
     */
    public void setStop(boolean stop) {
        this.stop = stop;
    }

    /**
     * Setter für Externen Takt.
     * @param value
     */
    public void setRA4(boolean value) {
        picView.getBoxA4().setSelected(value);
    }

    /**
     * Erstellt einen neuen COM Port, übergibt die aktuellen Werte
     * von TRIS A und B, sowie Port A und B. Erstellt eine Verbindung
     * zu übergebenem COM Port.
     * @param comPort COM-Port Nummer.
     */
    public void connectComPort(String comPort) {
        int trisA, trisB, portA, portB;
        trisA = Register.getInstance().getRegisterByteForGUI(0x85);
        trisB = Register.getInstance().getRegisterByteForGUI(0x86);
        portA = Register.getInstance().getRegisterByteForGUI(0x5);
        portB = Register.getInstance().getRegisterByteForGUI(0x6);
        com = new COMPort(trisA, portA, trisB, portB);
        try {
            com.connect(comPort);
        } catch (Exception ex) {
            Logger.getLogger(Pic.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Schließt die aktuelle Verbindung und setzt ComPort-Objekt auf null.
     */
    public void closeComPort() {
        com.close();
        com = null;
    }

    /**
     * Kommuniziert mit COM-Port. Gibt TRIS und Port Werte an COM aus
     * und liest neue Port Werte ein.
     */
    private void refreshComPort() {
        int trisA, trisB, portA, portB;
        trisA = Register.getInstance().getRegisterByteForGUI(0x85);
        trisB = Register.getInstance().getRegisterByteForGUI(0x86);
        portA = Register.getInstance().getRegisterByteForGUI(0x5);
        portB = Register.getInstance().getRegisterByteForGUI(0x6);
        com.updatePortA(portA);
        com.updatePortB(portB);
        com.updateTrisA(trisA);
        com.updateTrisB(trisB);

        portA = com.getInputPortA();
        portB = com.getInputPortB();

        Register.getInstance().setRegisterByteAsIntForGUI(0x5, portA);
        Register.getInstance().setRegisterByteAsIntForGUI(0x6, portB);
    }

    /**
     * @return the com
     */
    public COMPort getCom() {
        return com;
    }
}
	
